#include <glib.h>
#include <enet/enet.h>
#include <stdio.h>

#include "netdata.h"
#include "logging.h"

ENetHost * server;
int clients = 0;
ENetPeer * leader = NULL;
gboolean paused = FALSE;
gint port = PORT;

static GOptionEntry entries[] = 
{
  { "port", 'p', 0, G_OPTION_ARG_INT, &port, "UDP port to run server on", "PORT" },
  { NULL }
};


ENetPacket * create_instruction_packet(gchar instruction, enet_uint32 flags) {
    return enet_packet_create(&instruction,1,flags);
}

void send_instruction_peer(ENetPeer * peer,gchar instruction,enet_uint32 flags) {
    enet_peer_send(peer, 0, create_instruction_packet(instruction,flags));
}

/* 
 * Most of this function is ripped from enet's enet_packet_broadcast
 */
void send_everyone (ENetPacket * packet) {
    ENetPeer * currentPeer;

    for (currentPeer = server -> peers;
         currentPeer < &server -> peers [server -> peerCount];
         ++ currentPeer)
    {
        if (currentPeer -> state != ENET_PEER_STATE_CONNECTED)
            continue;
        if (currentPeer == leader)
            continue;
        enet_peer_send (currentPeer, 0, packet);
    }

    if (packet -> referenceCount == 0)
        enet_packet_destroy (packet);
}

gchar server_handler() {
    ENetEvent event;
    while (enet_host_service (server, &event, SERVER_WAIT) >= 0) {
        switch (event.type) {
        case ENET_EVENT_TYPE_CONNECT:
            event.peer -> data = NULL;
            if ((leader == NULL) && (clients == 0)) {
                g_debug("No others conencted, giving leader...");
                leader = event.peer;
                send_instruction_peer(event.peer, 
                    LEADER,ENET_PACKET_FLAG_RELIABLE);
            }
            
            if (paused) {
                send_instruction_peer(event.peer, 
                    PAUSE,ENET_PACKET_FLAG_RELIABLE);
            }
            clients += 1;
            break;

        case ENET_EVENT_TYPE_RECEIVE:
            switch (event.packet->data[0]) {
                            case PING:
                                g_debug("PONG! (not implemented)\n");
                                break;
                            case PAUSE:
                                // Forward pause
                                if (event.peer == leader) {
                                    send_everyone(event.packet);
                                    paused = !paused;
                                    break;
                                }
                            case SYNC:
                                if (event.peer == leader) {
                                    send_everyone(event.packet);
                                }
                                break;
                            case FORCE_SYNC:
                                // If we need to force a sync push
                                enet_peer_send(leader, 0, event.packet);
                                break;
                            case FIGHT:
                                if ((leader == NULL) && (clients > 0)) {
                                    leader = event.peer;
                                    send_instruction_peer(event.peer,
                                        LEADER,ENET_PACKET_FLAG_RELIABLE);
                                }
                                break;
                            default:
                                g_warning("Bad instruction recieved, skip!");
                                break;
                        }
            break;
           
        case ENET_EVENT_TYPE_DISCONNECT:
            clients -= 1;
            g_debug ("%d left", clients);
            if (clients == 0) {
                leader = NULL;
                paused = FALSE;
            } else if (event.peer == leader) {
                leader = NULL;
                enet_host_broadcast(server, 0, 
                    create_instruction_packet(FIGHT,0));
            }
            /* Reset the peer's client information. */
            event.peer -> data = NULL;
         default:
            break;
        }
    }
    return 1;
}

int main (int argc, char ** argv) {
    GError * error = NULL;
    GOptionContext * context;

    context = g_option_context_new ("- mplayer-share v1 server");
    g_option_context_add_main_entries (context, entries, NULL);
    if (!g_option_context_parse (context, &argc, &argv, &error)) {
        g_critical ("option parsing failed: %s\n", error->message);
        return EXIT_FAILURE;
    }
    
    g_log_set_handler (NULL, G_LOG_LEVEL_DEBUG, log_debug, NULL);
    
    if (enet_initialize () != 0) {
        g_critical ("An error occurred while initializing ENet.\n");
        return EXIT_FAILURE;
    }
    atexit (enet_deinitialize);

    ENetAddress address;

    address.host = ENET_HOST_ANY;
    address.port = port;
    server = enet_host_create (&address, 32, 2, 0, 0);
    
    if (server == NULL) {
        g_critical ("An error occurred while trying to create an ENet server host.\n");
        exit (EXIT_FAILURE);
    }
    
    gchar exit = NONE;
    while (!exit) {
        exit=server_handler();
        switch(exit){
		case(NONE):
		case(ERROR):
			exit=ERROR;
			g_warning("Server closed with an error\n");
			break;
		case(CLEAN):
			g_debug("Server closed cleanly\n");
			break;
		default:
			exit=ERROR;
			g_warning("Server closed with an invalid status\n");
			break;
		}
    }
    
    enet_host_destroy(server);
    return EXIT_SUCCESS;
}
